stream: allow null as second arg in Transform callback#62803
stream: allow null as second arg in Transform callback#62803galaxy4276 wants to merge 2 commits intonodejs:mainfrom
Conversation
callback(null, null) in a Transform._transform method should be equivalent to calling this.push(null) followed by callback(), ending the readable side of the stream. Previously val != null blocked the push because null == null is true in loose equality, so push(null) was never called and the stream never emitted 'end'. Change the guard from `val != null` to `val !== undefined` so that null passes through to this.push(), which sets state.ended and eventually emits 'end', matching documented behavior. Fixes: nodejs#62769 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Review requested:
|
|
Looking at the history, The fix changes to
In practice, That said, I'm uncertain whether this is semver-patch (bug fix aligning with docs) or semver-minor (observable behavior change). Would appreciate guidance on the right label and whether a CHANGELOG note is needed. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #62803 +/- ##
==========================================
- Coverage 89.70% 89.69% -0.01%
==========================================
Files 706 706
Lines 218288 218222 -66
Branches 41782 41767 -15
==========================================
- Hits 195806 195742 -64
+ Misses 14394 14381 -13
- Partials 8088 8099 +11
🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mcollina
left a comment
There was a problem hiding this comment.
This should have a pass of citgm before landing.
I expect a significant breakage in the ecosystem.
Thank you for your review. |
What
The docs say passing a value as the second argument to the transform callback is equivalent to calling
transform.push(value). That impliescallback(null, null)should pushnull— ending the readable side — just likethis.push(null); callback()does. It doesn't.Bug
The
'end'event never fires. The stream hangs.Root cause —
lib/internal/streams/transform.js:val != nulluses loose equality, so bothnullandundefinedfail the check.this.push(null)is never called,state.endedstaysfalse, and'end'is never emitted.Fix
Now
callback(null, null)reachesthis.push(null), which setsstate.ended = trueand eventually emits'end'once the buffer drains — matching what the docs describe.Behavior after fix
Potential concern
This is technically a behavior change. Code that relied on
callback(null, null)not ending the stream should usecallback()(no second argument) instead. I think this is the right call given the docs, but open to pushback — especially on whether this warrants a semver-minor label or a deprecation note before changing.